package simulator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class Spider {

	static Logger log = Logger.getLogger(Spider.class.getName());

	// grade code
	public static final int EASY = 1;
	public static final int NATURAL = 2;
	public static final int HARD = 3;

	private int grade = 0;

	private Hashtable<Point, Card> tableau = null;
	private ArrayList<Move> moveRecord = null;

	// private Card cardsEASY[] = new Card[104];
	// private Card cardsNATURAL[] = new Card[104];
	// private Card cardsHARD[] = new Card[104];

	private int dealturn = 0;
	private int finish = 0;

	// ////////////////
	// public List<Card> lcardsEASY = new LinkedList<Card>();
	// public List<Card> lcardsNATURAL = new LinkedList<Card>();
	// public List<Card> lcardsHARD = new LinkedList<Card>();

	public List<Card> currentCards = new LinkedList<Card>();

	// data record
	private int totalMove = 0;

	private void linitCards(int grade) {
		int k;
		if (grade == 3) {
			k = 4;
		} else {
			k = grade;
		}
		// init three suits cards for each difficulty
		for (int i = 1; i <= 8; i++) {
			for (int j = 1; j <= 13; j++) {
				this.currentCards.add(new Card((i % k + 1) + "-" + j, this));
				// this.lcardsNATURAL.add(new Card((i % 2 + 1) + "-" + j,
				// this));
				// this.lcardsHARD.add(new Card((i % 4 + 1) + "-" + j, this));
			}

		}

	}

	public void lsetCardsLocation(List<Card> cards) {
		this.tableau = new Hashtable<Point, Card>();

		int x = 0;
		int y = 0;
		for (int i = 0; i < 6; i++) {
			for (int j = 0; j < 10; j++) {
				int n = i * 10 + j;
				x = 10;
				Point point = new Point(x, y);
				cards.get(n).setFront(false);
				cards.get(n).setLocation(point);
				// System.out.println(point+"|"+cards.get(n).getName());
				tableau.put(point, cards.get(n));
				y++;
			}

		}

		x = 0;
		y = 0;
		for (int i = 6; i < 11; i++) {
			for (int j = 0; j < 10; j++) {
				int n = i * 10 + j;
				if (n >= 104)
					continue;
				Point point = new Point(x, y);
				cards.get(n).setFront(false);
				cards.get(n).setLocation(point);
				tableau.put(point, cards.get(n));
				x++;
			}
			x = 0;
			y++;
		}
	}

	// ///////////////////////
	public Spider(int grade) {
		super();
		this.grade = grade;
		PropertyConfigurator.configure("log4j.properties");
		// this.initCards();
		this.init();

	}

	private void init() {

		this.linitCards(this.grade);

		// switch (grade) {
		// case 1:
		// this.currentCards = lcardsEASY;
		// break;
		// case 2:
		// this.currentCards = lcardsNATURAL;
		// break;
		// case 3:
		// this.currentCards = lcardsHARD;
		// break;
		// default:log.warn("Wrong grade!");
		// throw new IllegalArgumentException();
		// }

		Collections.shuffle(this.currentCards);
		log.info("shuffle");
		this.lsetCardsLocation(this.currentCards);
		log.info("build tableau");
		// initiate the move record;
		moveRecord = new ArrayList<Move>();

	}

	public void setGrade(int gradeCode) {
		this.grade = gradeCode;
	}

	// private void initCards() {
	// // init three suits cards for each difficulty
	// for (int i = 1; i <= 8; i++) {
	// for (int j = 1; j <= 13; j++) {
	// this.cardsEASY[(i - 1) * 13 + j - 1] = new Card((i % 1 + 1)
	// + "-" + j, this);
	// this.cardsNATURAL[(i - 1) * 13 + j - 1] = new Card((i % 2 + 1)
	// + "-" + j, this);
	// this.cardsHARD[(i - 1) * 13 + j - 1] = new Card((i % 4 + 1)
	// + "-" + j, this);
	// }
	//
	// }
	//
	// }

	// public void randomCards(Card[] cards) {
	// Card temp = null;
	// for (int i = 0; i < 52; i++) {
	// int a = (int) (Math.random() * 104);
	// int b = (int) (Math.random() * 104);
	// temp = cards[a];
	// cards[a] = cards[b];
	// cards[b] = temp;
	// }
	// }

	// public void setCardsLocation(Card[] cards) {
	// this.tableau = new Hashtable<Point, Card>();
	//
	// int x = 0;
	// int y = 0;
	// for (int i = 0; i < 6; i++) {
	// for (int j = 0; j < 10; j++) {
	// int n = i * 10 + j;
	// x = 10;
	// Point point = new Point(x, y);
	// cards[n].setFront(false);
	// cards[n].setLocation(point);
	// tableau.put(point, cards[n]);
	// y++;
	// }
	//
	// }
	//
	// x = 0;
	// y = 0;
	// for (int i = 10; i > 5; i--) {
	// for (int j = 0; j < 10; j++) {
	// int n = i * 10 + j;
	// if (n >= 104)
	// continue;
	// Point point = new Point(x, y);
	// cards[n].setFront(false);
	// cards[n].setLocation(point);
	// tableau.put(point, cards[n]);
	// x++;
	// }
	// x = 0;
	// y++;
	// }
	// }

	/**
	 * 
	 * 
	 * @param column
	 * @return
	 */
	public Point getLastCardLocation(int column) {
		Point point = new Point(column, 0);
		Card card = this.tableau.get(point);
		if (card == null) {
			log.info("No card in this column. Column: " + column);
			return null;
		}
		while (card != null) {
			point = card.getLocation();
			card = this.getNextCard(card);

		}
		return point;
	}

	/**
	 * 
	 * 
	 * @param card
	 * @return
	 */
	public Card getNextCard(Card card) {
		Point point = new Point(card.getLocation().getX(), card.getLocation()
				.getY() + 1);
		card = this.tableau.get(point);

		if (card != null)
			log.debug("Next Point is:" + point.toString()
					+ "        Next card is: " + card.getName());

		return card;

	}

	public Card getPreviousCard(Card card) {
		Point point = new Point(card.getLocation().getX(), card.getLocation()
				.getY() - 1);
		if (point.getY() < 0) {
			log.info("       |" + point.getX() + "." + point.getY()
					+ "| No previous card.");
			return null;

		}

		card = this.tableau.get(point);

		return card;
	}

	/**
	 * 
	 * 
	 * @param card
	 * @param target
	 * @return
	 */
	public boolean move(Card card, Point target) {
		// check whether the card is movable.
		if (!card.isMoveable()) {
			log.info("Card " + card.getName() + "@" + card.getLocation()
					+ " cannot be moved.");
			return false;
		}
		// get available target point list.
		ArrayList<Point> available = this.availableColumn(card);

		// check whether target point is available.

		// Card targetCard = this. tableau.get(target);
		// if (targetCard.getType()!=card.getType() ||
		// targetCard.getValue()-card.getValue() != 1) {
		// log.debug("Unavailable move.");
		// return false;
		// }

		if (!available.contains(target)) {
			log.info("Unavailable move. " + card + " to " + target);
			return false;
		}

		// if (target.getY()==0&&this.tableau.get(target)==null) {
		//
		// }

		Point pFrom = card.getLocation();

		// check whether is the card at last point
		if (card.getLocation().getY() == this.getLastCardLocation(pFrom.getX())
				.getY()) {
			// move card
			this.tableau.remove(card.getLocation());
			card.setLocation(target);
			tableau.put(target, card);
			log.info("move card : " + card.getType() + "-" + card.getValue()
					+ " from " + pFrom.getX() + "." + pFrom.getY() + " to "
					+ target.getX() + "." + target.getY());
		} else {

			Card nextCard = this.getNextCard(card);
			for (int i = 0; card != null; i++) {

				this.tableau.remove(card.getLocation());
				card.setLocation(target);
				tableau.put(target, card);
				log.info("move card : " + card.getType() + "-"
						+ card.getValue() + " from " + pFrom.getX() + "."
						+ pFrom.getY() + " to " + target.getX() + "."
						+ target.getY());
				card = nextCard;
				if (card != null)
					nextCard = this.getNextCard(card);

				target = new Point(target.getX(), target.getY() + 1);
			}
		}

		// reset the movability of the two column
		this.resetMovability(pFrom.getX());
		this.resetMovability(target.getX());

		// **|need to check whether a suit has been finished after the move|
		if (this.checkFinish(target.getX()))
			this.finish(target.getX());

		Move m = new Move(pFrom, target);
		this.moveRecord.add(m);

		totalMove++;
		return true;
	}

	/**
	 * cancel LAST move not including deal. **|need to deal with finishing|
	 * 
	 * @return
	 */
	public boolean cancelMove() {
		Move lastMove = this.moveRecord.remove(this.moveRecord.size() - 1);
		if (lastMove == null) {
			log.info("no more available cancel");
			return false;
		} else {
			Move cancel = lastMove.reverse();

			// **|need to edit tableau| copy from move
			// check whether is the card at last point
			Card card = this.tableau.get(cancel.getpFrom());
			Point pFrom = cancel.getpFrom();
			Point target = cancel.getpTo();

			if (card.getLocation().getY() == this.getLastCardLocation(
					pFrom.getX()).getY()) {
				// move card
				this.tableau.remove(card.getLocation());
				card.setLocation(target);
				tableau.put(target, card);
			} else {

				Card nextCard = this.getNextCard(card);
				for (int i = 0; nextCard != null; i++) {
					target = new Point(target.getX(), target.getY() + i);
					this.tableau.remove(card.getLocation());
					card.setLocation(target);
					tableau.put(target, card);
					card = nextCard;
					nextCard = this.getNextCard(card);
				}
			}

			// reset the movability of the two column
			this.resetMovability(pFrom.getX());
			this.resetMovability(target.getX());

			return true;
		}

	}

	/**
	 * 
	 * 
	 * @param card
	 * @return
	 */
	public ArrayList<Point> availableColumn(Card card) {
		ArrayList<Point> availableCol = new ArrayList<Point>();
		for (int i = 0; i < 10; i++) {
			Point targetPoint = this.getLastCardLocation(i);
			if (targetPoint == null) {

				availableCol.add(new Point(i, 0));

				log.info("target column is blank!");
			} else {

				Card targetCard = tableau.get(targetPoint);
				if (card == null) {
					log.info("card null");
				}
				if (targetCard == null) {
					log.info("targetPoint : " + targetPoint);
					log.info("targetCard null");
				}

				if (targetCard.getValue() - card.getValue() == 1) {
					availableCol.add(new Point(this.getLastCardLocation(i)
							.getX(), this.getLastCardLocation(i).getY() + 1));
				}
			}

		}
		// log.info("ArrayList<Point> availableCol         size : " +
		// availableCol.size());
		return availableCol;
	}

	/**
	 * **|need to check whether is it needed to remove cards form col 11 while
	 * dealing.|
	 */
	public void deal() {
		if (dealturn < 60) {
			for (int i = 0; i < 10; i++) {

				if (this.getLastCardLocation(i) == null) {
					log.info(" Not allowed to deal with Empty column!");
					return;
				}

			}

			for (int i = 0; i < 10; i++) {

				Point lastPoint = this.getLastCardLocation(i);
				Point target = new Point(i, lastPoint.getY() + 1);
				Card card = currentCards.get(dealturn + i);
				this.tableau.remove(card.getLocation());
				card.setLocation(target);
				tableau.put(target, card);
				card.setFront(true);
				// card.setMoveable(true);
				// **|need to check whether previous cards movable after deal|
				this.resetMovability(i);
				if (this.checkFinish(i))
					this.finish(i);
			}

			log.info("DEAL      dealturn : " + dealturn);
			dealturn += 10;

			// deal is unable to cancel
			moveRecord = new ArrayList<Move>();

		} else {
			log.info("No more cards available!");
			return;
		}

	}

	/**
	 * check and reset the movability of the cards for target column after deal.
	 * 
	 * @param column
	 * @return
	 */
	public void resetMovability(int column) {
		Point lastPoint = this.getLastCardLocation(column);

		// no need to reset blank column
		if (lastPoint == null)
			return;

		Card card = this.tableau.get(lastPoint);
		// **!possible bug point!

		if (card == null) {
			log.warn("Cannot get the last card of  column " + column
					+ " . Resetting failed.");
			return;
		}
		// set the last card movable and turn front.
		card.setMoveable(true);
		card.setFront(true);
		Card previousCard = this.getPreviousCard(card);

		while (previousCard != null && previousCard.isFront()) {
			if (card.isMoveable() && previousCard.getType() == card.getType()
					&& previousCard.getValue() - card.getValue() == 1) {
				previousCard.setMoveable(true);
			} else {
				previousCard.setMoveable(false);
			}
			card = previousCard;
			previousCard = this.getPreviousCard(card);
		}

	}

	/**
	 * 
	 * 
	 * @param column
	 * @return
	 */
	public boolean checkFinish(int column) {
		Point point = this.getLastCardLocation(column);
		Card cardStart = this.tableau.get(point);
		if (cardStart != null && cardStart.getValue() == 1
				&& point.getY() >= 12) {
			Card cardEnd = this.tableau.get(new Point(point.getX(), point
					.getY() - 12));
			if (cardEnd != null && cardEnd.isFront() && cardEnd.isMoveable()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * just remove the finshed cards
	 * 
	 * @param column
	 */
	public void finish(int column) {
		Point point = this.getLastCardLocation(column);
		for (int i = 0; i < 13; i++) {
			// **!possible bug point!
			this.tableau.remove(new Point(point.getX(), point.getY() - i));
		}

		this.finish++;
		this.resetMovability(column);
		// finishing is unable to cancel.
		moveRecord = new ArrayList<Move>();
		if (this.finish == 8)
			log.warn("Win.");
		// **|need a finishing machanism.|

	}

	// public Card[] getCardsEASY() {
	// return cardsEASY;
	// }
	//
	// public Card[] getCardsNATURAL() {
	// return cardsNATURAL;
	// }
	//
	// public Card[] getCardsHARD() {
	// return cardsHARD;
	// }

	public Hashtable<Point, Card> getTableau() {
		if (this.tableau == null) {
			throw new NullPointerException(
					"Spider.tableau has not been initiated!");
		} else {
			return this.tableau;
		}

	}
	
	/**
	 * 
	 */
	public  void show() {

		int n = 0;
		int k = 0;
		System.out.print("CARDS\t ");
		for (int m = 0; m < 11; m++) {
			System.out.print("|COL" + m + "\t|");
		}
		System.out.println();
		System.out
				.println("=================================================================================");
		for (int i = 0; i < 60; i++) {

			System.out.print("ROW " + i + "\t:");
			for (int j = 0; j < 11; j++) {

				Point p = new Point(j, i);
				Card card = this.getTableau().get(p);
				if (card == null) {
					System.out.print("|    \t|");

				} else {
					System.out.print("|");
					if (!card.isFront())
						System.out.print("#");
					if (card.isMoveable())
						System.out.print("*");
					System.out.print(card.getName() + "\t|");

				}
				k++;
				if (k != 0 && k % 11 == 0) {
					System.out.println("");
				}
			}

		}

		System.out
				.println("==================================================================");
		Point p = this.getLastCardLocation(8);
		System.out
				.println("=============================================================");
		// log.debug(p.toString());

	}
	
	
	/**
	 * 
	 *
	 * @return
	 */
	public  boolean checkOriginalBug() {
		int judge = 10;
		for (int i = 0; i < 10; i++) {
			if (this.getLastCardLocation(i) == null) {
				judge--;

			} else if (this.getLastCardLocation(i).getY() == 0) {
				judge--;
			}
		}
		if (judge == 0)
			return true;

		return false;
	}
	
	
	
	
	/**
	 * 
	 * @param arr
	 * @param s
	 * @return
	 */
	public  Point getMaxColumn(ArrayList<Point> arr) {

		int max = -1;
		Point pMax = null;
		for (int i = 0; i < arr.size(); i++) {
			int k = 1;
			Point p = arr.get(i);
			if (p.getY() > 0) {
				Card c = this.getTableau().get(this.getLastCardLocation(p.getX()));
				while ((c = this.getPreviousCard(c)) != null) {
					if (c.isMoveable()) {
						k++;
					}

				}
				if (k > max) {
					max = k;
					pMax = p;
				}
			} else {
				k = 0;
				if (k > max) {
					max = k;
					pMax = p;
				}
			}
		}

		return pMax;
	}
	
	
	/**
	 * 
	 * @return
	 */
	public int searchBlank() {

		for (int i = 0; i < 10; i++) {
			if (this.getLastCardLocation(i) == null)
				return i;
		}
		return -1;
	}
	
	
	/**
	 * 
	 * @param s
	 * @return
	 */
	public boolean checkAllMovable() {
		int unmovable = 10;
		for (int i = 0; i < 10; i++) {
			if (this.getLastCardLocation(i) == null) {
				unmovable--;
			} else if (this.getTableau().get(new Point(i, 0)).isMoveable()) {
				unmovable--;
			}
		}
		if (unmovable == 0) {
			return true;
		} else {
			return false;
		}
	}
	
	
	/**
	 * 
	 * @return
	 */
	public void fillBlank() {
		for (int i = 0; i < 10; i++) {
			Point last = this.getLastCardLocation(i);
			if (last != null) {
				Card head = this.getMovableHeadCard( last);
				Card next = this.getNextCard(head);
				int blankCol = this.searchBlank();
				while (blankCol > -1 && next != null) {

					Point blank = new Point(blankCol, 0);
					this.move(next, blank);
					next = this.getNextCard(next);
					blankCol = this.searchBlank();
				}
			}

		}

	}
	
	/**
	 * 
	 * @param s
	 * @param lastPoint
	 * @return
	 */
	public Card getMovableHeadCard(Point lastPoint) {
		Card hold = this.getTableau().get(lastPoint);

		while (hold.getLocation().getY() > 0
				&& this.getPreviousCard(hold).isMoveable()) {
			hold = this.getPreviousCard(hold);
		}
		return hold;
	}
	
	

	public int getFinish() {
		return finish;
	}

	public int getDealturn() {

		return dealturn;
	}

	public int getTotalMove() {
		return totalMove;
	}

}
